import os
import shutil
import mimetypes
import hashlib

from mtp.model import *
from mtp.model import ObjBase
from mtp.lib.plugin import Plugin
from mtp.lib.pageaction import PageAction
from mtp.lib.base import *

from lxml import etree
from lxml import html
from lxml.html import builder as E
from lxml import cssselect

from paste.fileapp import FileApp

import logging
log = logging.getLogger(__name__)

MimeType = String(255)

AttachmentsTable = Table('attachments', plugin_meta,
    Column('id', Integer, primary_key=True, autoincrement=True),
    Column('page_id', Integer, ForeignKey(PagesTable.c.id)),
    Column('name', String(255)),
    Column('mime', MimeType),
    Column('author_id', Integer, ForeignKey(UsersTable.c.id)),
    Column('created', DateTime, default=datetime.now),
    Column('revision', Integer),
    Column('comment', String(255)),
    Column('md5', MD5Type),
)

class Attachment(ObjBase):
    @classmethod
    def getForPage(klass, page_id):
        return (Session.query(klass)
                .filter(klass.page_id==page_id)
                .order_by(klass.revision.desc())
                .group_by(klass.name)
                .order_by(klass.name)
        )
    
    @classmethod
    def get(klass, page, name):
        try:
            return (Session.query(klass)
                    .filter(klass.page_id==page.id)
                    .filter(klass.name==name)
                    .order_by(klass.revision.desc())
                    .first()
            )
        except InvalidRequestError:
            return None

    @classmethod
    def getAllRevisions(klass, page, name):
        return (Session.query(klass)
                .filter(klass.page_id==page.id)
                .filter(klass.name==name)
                .order_by(klass.revision.desc())
                .all()
        )

    @classmethod
    def addToPage(klass, page, name, infile, user=None, comment=None):
        log.info('Creating attachment for page "%s" name=%s' % (page.title, name))

        m = hashlib.md5()
        m.update(infile.read())
        infile.seek(0)
        md5sum = m.hexdigest()

        last_version = Attachment.get(page, name)
        if last_version:
            rev = last_version.revision + 1
            if md5sum == last_version.md5:
                return last_version
        else:
            rev = 1

        mime, encoding = mimetypes.guess_type(name)
        
        attachment = klass(
            page_id=page.id,
            name=name,
            author_id=(user.id if user else None),
            revision=rev,
            comment=comment, 
            mime=mime,
            md5=md5sum,
        )

        attachment.save()

        file_loc = attachment.filename()
        file_dir = os.path.split(file_loc)[0]
        if not os.path.exists(file_dir):
            os.makedirs(file_dir)
        log.debug('file_loc %s' % file_loc)
        shutil.copyfileobj(infile, open(file_loc, 'w'))
 
        return attachment

    def delete(self):
        os.unlink(self.filename)
        ObjBase.delete(self)

    @classmethod
    def deleteAllRevisions(klass, page, name):
        revisions = klass.getAllRevisions(page, name)
        for attachment in revisions:
            os.unlink(attachment.filename())
            Session.delete(attachment)
        Session.commit()

    def filename(self):
        return os.path.join(config['mtp.plugins']['attachments'].dir,
                self.page.urlTitle(), self.name + '.%03d' % self.revision)

    def textContent(self):
        return open(self.filename()).read()


mapper(Attachment, AttachmentsTable, properties={
    'author': relation(User),
    'page': relation(Page),
})


class AttachmentsController(BaseController):
    @needs_perm('attachment', 'show')
    def index(self, format='html'):
        return "attachment list!"    

    @needs_perm('attachment', 'show')
    def show(self, page_id, id):
        page = Page.getByTitle(page_id)
        if page is None: abort(404)
        attachment = Attachment.get(page, id)
        if attachment is None: abort(404)
        file_loc = attachment.filename()
        if not os.path.exists(file_loc):
            abort(404)
        app = FileApp(file_loc)
        return app(request.environ, self.start_response)

    @needs_perm('attachment', 'create')
    def create(self, page_id):
        action = request.params.get('action').lower()
        if action != 'cancel':
            page = Page.getByTitle(page_id)
            if not page:
                abort(404)

            infile = request.POST['thefile']
            Attachment.addToPage(page, infile.filename, infile.file, 
                                        user=session.get('user'))
            
        redirect_to( h.url_for('page', id=page_id) )

    @needs_perm('attachment', 'create')
    def new(self, page_id):
        c.page = Page.getByTitle(page_id)
        return render('/attachments/new.html')

    @needs_perm('attachment', 'show')
    def info(self, page_id, id):
        c.page = Page.getByTitle(page_id)
        if not c.page: abort(404)
        c.attachment = Attachment.get(c.page, id)
        if not c.attachment: abort(404)
        attachment_path = c.attachment.filename()
        c.attachment_size = os.path.getsize(attachment_path)
        c.revisions = Attachment.getAllRevisions(c.page, id)

        if has_perm('attachment', 'delete'):
            c.page_actions.append(PageAction('Delete Attachment', 
                h.url_for('page_attachment', page_id=page_id, id=id), method="DELETE"))
        
        return render('/attachments/info.html')

    @needs_perm('attachment', 'delete')
    def delete(self, page_id, id):
        page = Page.getByTitle(page_id)
        if not page: abort(404)
        Attachment.deleteAllRevisions(page, id)
        redirect_to(h.url_for('page', id=page_id))


def image_macro(arg, element):
    """Insert an image"""
    if has_perm('attachment', 'show'):
        return E.IMG(src=h.url_for('page_attachment', 
                                    page_id=c.page.urlTitle(), id=arg.strip()))

def attachment_macro(arg, element):
    """Insert a link to download an attachment"""
    if has_perm('attachment', 'show'):
        name = arg.strip()
        return E.A("Attachment: %s" % name, 
                   href=h.url_for('page_attachment', 
                                  page_id=c.page.urlTitle(), id=name)) 

class AttachmentsPlugin(Plugin):
    tables = [AttachmentsTable]
    configspec = """
[plugin:attachments]
dir = string(default="attachments")

[macro:image]
enabled = boolean(default=True)

[macro:attachment]
enabled = boolean(default=True)
"""

    def __init__(self, **options):
        self.__dict__.update(options)

        config['mtp.all_macros']['image'] = image_macro
        config['mtp.all_macros']['attachment'] = attachment_macro

        config['mtp.content.append'][('pages', 'show')].append(self.pageContent)
        config['mtp.page_actions'][('pages', 'show')].append(self.pageActions)
        config['mtp.extra_css'].append('/attachments/attachments.css')

        config['mtp.permissions']['attachment'].update(
            ('create', 'delete', 'show')
        )

        cont = 'attachments'
        config['mtp.controllers'][cont] = AttachmentsController
        #config['routes.map'].resource('attachment', 'attachments',
        #    parent_resource=dict(member_name='page', collection_name='pages'),
        #)

        # bleh... we want to use .'s in our id's but routes.resource wont let us 
        # so we map all the rules ourselves...
    
        map = config['mtp.map']

        map.pageConnect('attachments', 
            controller=cont, action='create',
            conditions=dict(method=['POST']))
        map.pageConnect('page_attachments', 
            'attachments', 
            controller=cont, action='index',
            conditions=dict(method=['GET']))
        map.pageConnect('formatted_page_attachments', 
            'attachments.:format', 
            controller=cont, action='index',
            conditions=dict(method=['GET']))
        map.pageConnect('page_new_attachment', 
            'attachments/new', 
            controller=cont, action='new',
            conditions=dict(method=['GET']))
        map.pageConnect('formatted_page_new_message', 
            'attachments/new.:(format)',
            controller=cont, action='new',
            conditions=dict(method=['GET']))
        map.pageConnect('attachments/*id', 
            controller=cont, action='update',
            conditions=dict(method=['PUT']))
        map.pageConnect('attachments/*id', 
            controller=cont, action='delete',
            conditions=dict(method=['DELETE']))
        map.pageConnect('page_info_attachment', 
            'attachments/:(id)/info', 
            controller=cont, action='info',
            conditions=dict(method=['GET']))
        #map.connect('formatted_edit_message', 
        #    'pages/:page_id/attachments/:(id)/edit.:format', 
        #    controller=cont, action='edit', 
        #    conditions=dict(method=['GET']))
        map.pageConnect('page_attachment', 
            'attachments/*id', 
            controller=cont, action='show',
            conditions=dict(method=['GET']))
        #map.connect('formatted_message', 
        #    'messages/:(id).:(format)', controller='messages', action='show',
        #    conditions=dict(method=['GET']))

    def setup(self):
        Plugin.setup(self)
        log.debug('Making attachments dir %s...' % self.dir)
        if not os.path.exists(self.dir):
            os.makedirs(self.dir)

        #UserPermission.grant(None, 'attachment', 'create')
        #UserPermission.grant(None, 'attachment', 'delete')
        UserPermission.grant(None, 'attachment', 'show')

    def pageContent(self):
        if has_perm('attachment', 'show'):
            c.attachments = Attachment.getForPage(c.page.id)
            if c.attachments.count():
                return mako_render('/attachments/list.html')

    def pageActions(self):
        return (
            PageAction("Add Attachment", 
                h.url_for("page_new_attachment", page_id=c.page.urlTitle()), 
            ),
        )


